#!/usr/bin/env python  # 指定脚本解释器为python
import sys, socket, time, threading  # 导入系统、套接字、时间、线程模块
import struct  # 导入用于处理二进制数据的结构模块
import selectors  # 导入用于I/O多路复用的选择器模块

from frp import ConnTool  # 从frp包导入ConnTool模块

#from ConnTool import *  # 被注释的导入语句
sel = selectors.DefaultSelector()  # 创建默认的选择器实例



class Frpc():  # 定义Frpc客户端类
    def __init__(self, serverhost,serverport, targethost,targetport):  # 初始化方法
        self.targethost=targethost  # 设置目标主机地址
        self.targetport=targetport  # 设置目标端口
        self.serverhost=serverhost  # 设置服务器主机地址
        self.serverport=serverport  # 设置服务器端口
        self.server_fd = socket.create_connection((self.serverhost,self.serverport))  # 创建到frps服务器的连接
        self.server_fd.sendall(struct.pack('i', 1))  # 启动时 首次发送心跳包 1
        self.server_fd.setblocking(False)  # 设置套接字为非阻塞模式
        sel.register(self.server_fd,selectors.EVENT_READ,self.handle_controller_data)  # 注册到选择器监听读事件

        self.workConnPool = []  # 初始化工作连接池
        threading.Thread(target=self.maitainConPool).start()  # 启动连接池维护线程
        threading.Thread(target=self.heartbeat).start()  # 启动心跳线程

    def heartbeat(self):  # 心跳方法
        while True:  # 循环发送心跳包
            if self.server_fd is not None:  # 如果服务器连接存在
                self.server_fd.send(struct.pack('i', 1))  # 发送心跳包(命令1)
            time.sleep(9)  # 每9秒发送一次心跳

    # 提前把 workConn和targetConn连接起来，需要的时候直接用workConn targetConn不需要缓存
    def maitainConPool(self):  # 维护连接池方法
        print("启动tcp连接池")  # 打印启动信息
        pool_size = 0  # 连接池大小(当前为0)
        while True:  # 循环维护连接池
            if len(self.workConnPool)<pool_size:  # 如果连接池小于设定大小
                workConn = socket.create_connection((self.serverhost,self.serverport))  # 创建到服务器的工作连接
                targetConn = socket.create_connection((self.targethost, self.targetport))  # 创建到目标服务的连接
                ConnTool.join(targetConn,workConn)  # 使用ConnTool建立双向数据传输
                self.workConnPool.append(workConn)  # 将工作连接加入连接池


    def handle_controller_data(self,server_fd, mask):  # 处理控制数据方法
        try:
            data= server_fd.recv(4)  # 接收4字节的命令数据
            if data:  # 如果接收到数据
                cmd = struct.unpack('i',data)[0]  # 解包获取命令码
                print('cmd:',cmd)  # 打印命令码
                if cmd ==2:  # 要求frpc建立的工作tcp
                    print('收到frps控制指令')  # 打印收到控制指令信息
                    # 直接从池子里获取
                    if len(self.workConnPool)>0:  # 如果连接池有可用连接
                        workConn = self.workConnPool.pop()  # 从连接池取出一个连接
                    else:  # 如果连接池为空
                        targetConn = socket.create_connection((self.targethost, self.targetport))  # 创建到目标服务的连接
                        workConn = socket.create_connection((self.serverhost,self.serverport))  # 创建到服务器的工作连接
                        ConnTool.join(targetConn,workConn)  # 建立双向数据传输
                    workConn.sendall(struct.pack('i',2)) # 1 心跳包  # 发送命令2表示工作连接
                    print("建立工作tcp")  # 打印建立工作连接信息
        except IOError as err:  # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常
            # sel.unregister(conn)
            # conn.close()
            # print(err)
            pass  # 捕获IO异常但不做处理

    def run(self):  # 运行方法
        while True:  # 循环处理事件
            events = sel.select()  # 使用选择器等待事件
            for key, mask in events:  # 遍历事件
                callback = key.data  # 获取回调函数
                callback(key.fileobj, mask)  # 调用回调函数处理事件
        print("frpc started!")  # 打印启动信息(实际上不会执行到这里)


if __name__ == '__main__':  # 主程序入口
    print('Starting frpc...')  # 打印启动信息
    try:
        """
        - `remothost`: frps 服务器的主机地址（IP 或域名）
        - `remoteport`: frps 服务器上用于接收 frpc 连接的端口（对应 frps 的 frpsport）
        - [targethost]: 需要被代理的本地服务主机地址
        - [targetport]: 需要被代理的本地服务端口
        """
        remothost = sys.argv[1]  # 获取命令行参数:frps服务器主机地址
        remoteport = int(sys.argv[2])  # 获取命令行参数:frps服务器端口
        targethost = sys.argv[3]  # 获取命令行参数:目标服务主机地址
        targetport = int(sys.argv[4])  # 获取命令行参数:目标服务端口
    except (ValueError, IndexError):  # 捕获参数错误异常
        print('Usage: %s remothost [remoteport] targethost [targetport]' % sys.argv[0])  # 打印使用说明
        sys.exit(1)  # 退出程序

    sys.stdout = open('forwaring.log', 'w')  # 重定向标准输出到日志文件
    Frpc(remothost,remoteport, targethost,targetport).run()  # 创建Frpc实例并运行
    print('success started! remote is  %s:%d \t target is %s:%d' % (remothost,remoteport, targethost,targetport))  # 打印成功启动信息
    # Frpc('192.168.1.101',7000, 'localhost',3389).run()  # 被注释的测试代码

